import os
import sys
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + "/..")
from utils.mqtt.mqtt import MqttAdmin
import signal
from misc import get_wan_info, Notify
from utils.thread.thread import thread_write_add
from utils.thread.thread import thread_except_add
from utils.thread.thread import thread_timer_add
from utils.thread.thread import thread_run
from utils.thread.thread import thread_del
from queue import Queue
from common_msg import *
from utils.thread.thread import thread_read_add
import time
import struct
import socket
from fcntl import ioctl
from select import select
from threading import Thread
from ipaddress import ip_network

from local_log import logger

NETWORK = '192.168.255.0/24'
NET_MASK = '255.255.255.0'
BUFFER_SIZE = 4096
MTU = 1500
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002

DIE_TIME = 60

MQTT_TOPIC_SERVER_DATA = 'server_data'
MQTT_BROKER = '119.28.43.241'
MQTT_PORT = 1883


def get_wan():
    ip, inf = get_wan_info()
    return inf if inf else 'eth0'

class Session(object):
    def __init__(
            self,
            tunAddr=None,
            tunMask = None,
            uuid=None):
        logger.debug(f'enter session init')
        super().__init__()
        self.uuid = uuid
        self.last_time = 0
        # 点分十进制转换
        self.strTunAddr = tunAddr
        self.strTunMask = tunMask
        self.tunAddr = socket.inet_aton(tunAddr)

    def update_time(self):
        self.last_time = int(time.time())

    def is_die(self):
        now = int(time.time())
        if self.last_time + DIE_TIME < now:
            return True
        return False


class SessionAdmin(object):
    def __init__(self, conf):
        logger.debug(f'enter sessionadmin init {type(conf)}')
        super().__init__()
        self.ses_list: list[Session] = []

    def add_session(self, ses: Session):
        self.ses_list.append(ses)

    def del_session(self, ses: Session):
        self.ses_list.remove(ses)

    def get_session_by_uuid(self, uuid):
        for it in self.ses_list:
            if it.uuid == uuid:
                return it
        return None

    def get_session_by_tunAddr(self, tunAddr):
        for it in self.ses_list:
            if it.tunAddr == tunAddr:
                return it
        return None

    def del_session_by_uuid(self, uuid):
        ses = self.get_session_by_uuid(uuid)
        if ses:
            self.del_session(ses)

    def del_session_by_tunAddr(self, tunAddr):
        ses = self.get_session_by_tunAddr(tunAddr)
        if ses:
            self.del_session(ses)

    def get_die_sessin(self):
        for it in self.ses_list:
            if it.is_die():
                return it
        return None


class TunConn(object):
    def __init__(self, conf):
        logger.debug(f'enter tunconn init{type(conf)}')
        super().__init__(conf)
        self.__conf = conf

        self.__th_read = None
        self.__th_write = None
        self.__list_write = []

        self.__tunfd, self.__tunName = self.__createTunnel()
        self.__startTunnel(self.__tunName)
        os.set_blocking(self.__tunfd, False)
        self.__reset_tun_read()

    def __del__(self):
        if self.__th_read:
            thread_del(self.__th_read)
            self.__th_read = None
        if self.__th_write:
            thread_del(self.__th_write)
            self.__th_write = None
        self.__list_write = []
        if self.__tunfd:
            os.close(self.__tunfd)
            self.__tunfd = None

    def __createTunnel(self, tunName='tun%d', tunMode=IFF_TUN):
        tunfd = os.open("/dev/net/tun", os.O_RDWR)
        ifn = ioctl(
            tunfd,
            TUNSETIFF,
            struct.pack(
                b"16sH",
                tunName.encode(),
                tunMode))
        tunName = ifn[:16].decode().strip("\x00")
        return tunfd, tunName

    def __startTunnel(self, tunName):
        # 加入转发iptables
        cmds = [
            'ifconfig %s %s netmask %s mtu %s up' % (tunName, self.__conf.local_ip, NET_MASK, MTU),
            'iptables  -t  nat  -D  POSTROUTING -s %s -o %s -j MASQUERADE' % (NETWORK, get_wan()),
            'iptables  -t  nat  -A  POSTROUTING -s %s -o %s -j MASQUERADE' % (NETWORK, get_wan()),
        ]
        for it in cmds:
            logger.debug(it)
            with os.popen(it) as f:
                logger.debug(f.readlines())

    @staticmethod
    def __tun_read(my_self, fd, args):
        my_self.__th_read = None
        my_self.__reset_tun_read()

        data = os.read(fd, BUFFER_SIZE)
        my_self.tun_receive(data)

    @staticmethod
    def __tun_write(my_self, fd, args):
        my_self.__th_write = None
        # logger.debug(f'enter __tun_write **********************************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
        if len(my_self.__list_write) == 0:
            return
        data = my_self.__list_write.pop(0)
        ret = os.write(fd, data)
        # logger.debug(f'tun write data len{len(data)} and ret is {ret}')
        my_self.__reset_tun_write()

    def __reset_tun_read(self):
        if self.__th_read:
            thread_del(self.__th_read)
            self.__th_read = None
        self.__th_read = thread_read_add(
            TunConn.__tun_read, self, self.__tunfd)

    def __reset_tun_write(self):
        # logger.debug(f'enter __reset_tun_write !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
        if self.__th_write:
            thread_del(self.__th_write)
            self.__th_write = None
        self.__th_write = thread_write_add(
            TunConn.__tun_write, self, self.__tunfd)

    def tun_write(self, data):
        if len(self.__list_write) == 0:
            self.__reset_tun_write()
        self.__list_write.append(data)
        if len(self.__list_write) > 5:
            logger.debug(f'enter tun_write list len is {len(self.__list_write)}')

    def tun_receive(self, data):
        logger.debug(f'this tun_receive func is for subclass use, so goto here is err!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')

class Server(TunConn, SessionAdmin, MqttAdmin, Notify):
    def __init__(self):
        logger.debug(f'enter server init')
        self.ip_range = list(map(str, ip_network(NETWORK)))[1:]
        # 去掉最后为0和255的项
        def is_valid_ip(ip:str):
            a = ip.split('.')
            if len(a) != 4:
                return False
            if int(a[3]) == 0 or int(a[3]) == 0xff:
                return False
            return True
        self.ip_range = list(filter(is_valid_ip, self.ip_range))
        # logger.debug(f'self.ip_range is {self.ip_range}')
        self.local_ip = self.ip_range.pop(0)
        super().__init__(self)
        # mqtt连接
        self.mqtt_connect(MQTT_BROKER, MQTT_PORT, 'tls/ca.crt')
        logger.debug(f'mqtt clientid is {self.mqtt_client_id()}')
        #异步线程通知消息用的
        self.msg_queue = Queue()
        # 开个定时器做会话超时检查
        self.th_session_die_check = None
        thread_timer_add(Server.session_die_check, self, DIE_TIME)        

    def mqtt_on_connect(self, client, userdata, flags, rc):
        # 走到这里是错误的，这个函数应该被继承实现
        if rc == 0:
            logger.debug(
                f"Connected to MQTT Broker! goto register top {MQTT_TOPIC_SERVER_DATA}")
            self.mqtt_subscribe(MQTT_TOPIC_SERVER_DATA)
        else:
            logger.debug("Failed to connect, return code %d\n", rc)

    def reapply_tunaddr(self, ses):
        cmsg = CommMsg(
            MSG_AUTH_RESULT, self.mqtt_client_id().encode(), ('%s;%s;%s'%(self.local_ip, ses.strTunAddr, ses.strTunMask)).encode())
        self.mqtt_publish(ses.uuid, cmsg.pack())

    def mqtt_on_message(self, client, userdata, msg):
        #线程间调用，所以用queue
        self.msg_queue.put_nowait(msg)
        self.notify()

    def notify_proc(self, data: bytes):
        while True:
            if self.msg_queue.empty():
                return
            msg = self.msg_queue.get_nowait()
            data = msg.payload
            cmsg = CommMsg()
            if cmsg.unpack(data) is False:
                logger.debug(
                    f'mqtt_on_message get error msg!!!!!!! len is {len(data)}')
                continue
            logger.debug(f'mqtt_on_message data len  {len(data)}')
            # logger.debug(f'mqtt_on_message data len  {len(data)} and uuid is {cmsg.uuid.decode()} and cmd is {cmsg.cmd}')
            ses = self.get_session_by_uuid(cmsg.uuid.decode())
            if not ses:
                ses = Session(self.ip_range.pop(0), NET_MASK, cmsg.uuid.decode())
                self.add_session(ses)
                # 返回下tunip
                self.reapply_tunaddr(ses)
                continue
            if cmsg.cmd == MSG_KEEPLIVE:
                logger.debug(f'get keepalive from {ses.uuid}')
                ses.update_time()
                continue
            if cmsg.cmd == MSG_DATA:
                srcAddr = cmsg.data[16:20]
                dstAddr = cmsg.data[20:24]
                if srcAddr != ses.tunAddr:
                    logger.debug(f'mqtt_on_message srcAddr {srcAddr} and ses.tunAddr {ses.tunAddr} is not same need reapply')
                    self.reapply_tunaddr(ses)
                    continue
                self.tun_write(cmsg.data)
                ses.update_time()
                continue
            logger.debug(f'mqtt_on_message recv err cmd {cmsg.cmd}')

    def tun_receive(self, data):
        srcAddr = data[16:20]
        dstAddr = data[20:24]
        # dump_hex('tun_receive', data[0:32])
        ses = self.get_session_by_tunAddr(dstAddr)
        if not ses:
            logger.debug(f'there is no ses by {dstAddr}')
            return
        logger.debug(f'mqtt pub datalen {len(data)}')
        cmsg = CommMsg(MSG_DATA, ses.uuid.encode(), data)
        self.mqtt_publish(ses.uuid, cmsg.pack())

    @staticmethod
    def session_die_check(local, fd, args):
        local.th_session_die_check = None
        thread_timer_add(Server.session_die_check, local, DIE_TIME)        
        logger.debug(f'die timer check')
        die_ses = local.get_die_sessin()
        if die_ses:
            # 回收ip
            local.ip_range.insert(0, die_ses.strTunAddr)
            logger.debug(f'reget ip {die_ses.strTunAddr} !!!!!!!!!!!!!!!!!!!!')
            local.del_session(die_ses)

    def run_forever(self):
        thread_run()

if __name__ == '__main__':
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    logger.debug(f'server __mro__ is {Server.__mro__}')
    server = Server()
    server.run_forever()
